package de.koller.oceanFramework.math;

import java.awt.geom.Point2D;

/**
 * 
 * @author Fabian Koller
 */
public class Vec implements ROVec {
	private double x = 0;
	private double y = 0;

	/**
	 * 
	 */
	public Vec() {
		
	}

	/**
	 * 
	 * @param vec
	 */
	public Vec( ROVec vec ) {
		this( vec.getX(), vec.getY() );
	}

	/** Generates a vector which lay within those two points.
	 * @param x1
	 * @param y1
	 * @param x2 
	 * @param y2  
	 */
	public Vec( double x1, double y1, double x2, double y2 ) {
		this( x2 - x1, y2 - y1 );
	}

	/**
	 * 
	 * @param point
	 */
	public Vec( Point2D point ) {
		this( point.getX(), point.getY() );
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 */
	public Vec(double x, double y) {
		this.setX( x );
		this.setY( y );
	}

	/**
	 * 
	 * @return
	 */
	public Vec normalize() {
		double l = this.getLength();
		if( l == 0 )
			throw new ArithmeticException( "Length is 0, cannot normalize!" );
		x /= l;
		y /= l;
		return this;
	}

	/** Reflect this vector from 'of' and returns the new vector.
	 * @param of
	 * @return  
	 */
	public Vec reflect( ROVec of ) {
		Vec v = new Vec( this );
		v.deltaDirection( of.getDirection() );
		return v;
	}

	/**
	 * 
	 */
	public void clear() {
		this.setX( 0 );
		this.setY( 0 );
	}

	/**
	 * 
	 * @param p
	 */
	public void set( Point2D p ) {
		this.setX( p.getX() );
		this.setY( p.getY() );
	}

	/**
	 * 
	 * @param vec
	 */
	public void set( ROVec vec ) {
		this.setX( vec.getX() );
		this.setY( vec.getY() );
	}

	/**
	 * 
	 * @param x
	 * @param y
	 */
	public void set( double x, double y ) {
		this.setX( x );
		this.setY( y );
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getLength() {
		return Math.sqrt( x*x + y*y );
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getDirection() {
		return Math.atan2( y, x );
	}

	/**
	 * 
	 * @param l
	 * @return
	 */
	public Vec setLength( double l ) {
		setTo( this.getDirection(), l );
		return this; 
	}

	/**
	 * 
	 * @param d
	 */
	public void setDirection( double d ) {
		setTo( d, this.getLength() );
	}

	/**
	 * 
	 * @param other
	 * @return
	 */
	@Override
	public double getDirection( ROVec other ) {
		Vec v = new Vec( this );
		v.subtract( other );
		return v.getDirection();
	}

	/**
	 * 
	 * @param direction
	 * @param length
	 */
	public void setTo( double direction, double length ) {
		this.setX( Math.cos( direction ) * length );
		this.setY( Math.sin( direction ) * length );
	}

	public Vec mult( double m ) {
		this.x *= m;
		this.y *= m;
		
		return this;
	}
	
	/** Gibt einen neuen Vec zurück der nur die Achsen besitzt dessen Flag gesetzt ist. */
	public Vec exclude( boolean x, boolean y ) {
		Vec v = new Vec( this );
		if( !x ) v.setX( 0 );
		if( !y ) v.setY( 0 );
		return v;
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 */
	public Vec add( double x, double y ) {
		this.setX( this.getX() + x );
		this.setY( this.getY() + y );
		return this;
	}
	
	/**
	 * 
	 * @param vec
	 * @return
	 */
	public Vec add( ROVec vec ) {
//		this.x += vec.getX();
//		this.y += vec.getY();

		this.setX( this.getX() + vec.getX() );
		this.setY( this.getY() + vec.getY() );
		return this;
	}

	/**
	 * 
	 * @param vec
	 */
	public Vec subtract( ROVec vec ) {
//		this.x -= vec.getX();
//		this.y -= vec.getY();

		this.setX( this.getX() - vec.getX() );
		this.setY( this.getY() - vec.getY() );
		return this;
	}

	/**
	 * 
	 * @param vec
	 * @return
	 */
	@Override
	public double distance( ROVec vec ) {
		return distance( vec.getX(), vec.getY() );
	}

	public double distanceSquare( ROVec other ) {
		double xd = other.getX() - this.getX();
		double yd = other.getY() - this.getY();
		return xd*xd + yd*yd;
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	@Override
	public double distance( double x, double y ) {
		return new Vec( this.getX() - x , this.getY() - y ).getLength();
	}

	/**
	 * 
	 * @param d
	 */
	public void deltaDirection( double d ) {
		this.setDirection( this.getDirection() + d );
	}

	/** Verschiebt diesen Vec zu den angegebenen Koordinaten.
	 * @param vec
	 * @return die Distanz des verschiebens. */
	public double translateTo( Vec vec ) {
		double d = this.distance( vec );
		this.setX( vec.getX() );
		this.setY( vec.getY() );
		return d;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public double translateTo( double x, double y ) {
		return translateTo( new Vec(x, y) );
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public Point2D getPoint2D() {
		return new Point2D.Double( x, y );
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getX() {
		return x;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getY() {
		return y;
	}

	/**
	 * 
	 * @param x
	 */
	public void setX(double x) {
		this.x = x;
	}

	/**
	 * 
	 * @param y
	 */
	public void setY(double y) {
		this.y = y;
	}

	@Override
	public String toString() {
		return "Vec: [ " + x + " / " + y + " ] l- " + this.getLength() + " d- " + this.getDirection();
	}

	@Override
	public int hashCode() {
		int hash = 7;
		hash = 59 * hash + ( int ) ( Double.doubleToLongBits( this.x ) ^ ( Double.doubleToLongBits( this.x ) >>> 32 ) );
		hash = 59 * hash + ( int ) ( Double.doubleToLongBits( this.y ) ^ ( Double.doubleToLongBits( this.y ) >>> 32 ) );
		return hash;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final Vec other = (Vec) obj;
		if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) {
			return false;
		}
		if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) {
			return false;
		}
		return true;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public float getXf() {
		return (float)x;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public float getYf() {
		return (float)y;
	}
	
	/**
	 * Uses Math#floor!
	 * @return
	 */
	@Override
	public int getXi() {
		return (int)Math.floor( x );
	}

	/**
	 * Uses Math#floor!
	 * @return
	 */
	@Override
	public int getYi() {
		return (int)Math.floor( y );
	}

	/**
	 * 
	 * @param z
	 * @return
	 */
	@Override
	public ROVec3 getVec3( double z ) {
		return new Vec3( this, z );
	}
	
	/**
	 * 
	 * @return
	 */
	@Override
	public ROVec3 getVec3() {
		return getVec3( 0 );
	}

	/**
	 * 
	 * @return
	 */
	public Vec newInstance() {
		return new Vec( this );
	}
	
	/**
	 * 
	 * @return
	 */
	public Vec getInverse() {
		return new Vec( -x, -y );
	}
}


